import "./styles.css";

import React, { forwardRef, useLayoutEffect, useRef, useState } from "react";
import {
  autoUpdate,
  size,
  useId,
  useDismiss,
  useFloating,
  useInteractions,
  useListNavigation,
  useRole
} from "@floating-ui/react-dom-interactions";
import { data } from "./data";

interface ItemProps {
  children: React.ReactNode;
  active: boolean;
}

const Item = forwardRef<
  HTMLLIElement,
  ItemProps & React.HTMLProps<HTMLLIElement>
>(({ children, active, ...rest }, ref) => {
  const id = useId();
  return (
    <li
      ref={ref}
      role="option"
      id={id}
      aria-selected={active}
      {...rest}
      style={{
        background: active ? "lightblue" : "none",
        padding: 4,
        cursor: "default",
        ...rest.style
      }}
    >
      {children}
    </li>
  );
});

function AutoComplete() {
  const [open, setOpen] = useState(false);
  const [inputValue, setInputValue] = useState("");
  const [activeIndex, setActiveIndex] = useState<number | null>(null);

  const listRef = useRef<Array<HTMLElement | null>>([]);

  const { x, y, reference, floating, strategy, context, refs } = useFloating<
    HTMLInputElement
  >({
    whileElementsMounted: (...args) =>
      autoUpdate(...args, { elementResize: false }),
    open,
    onOpenChange: setOpen,
    middleware: [
      size({
        apply({ rects, availableHeight, elements }) {
          Object.assign(elements.floating.style, {
            width: `${rects.reference.width}px`,
            maxHeight: `${availableHeight}px`
          });
        },
        padding: 10
      })
    ]
  });

  useLayoutEffect(() => {
    // IMPORTANT: When the floating element first opens, this runs when the
    // styles have **not yet** been applied to the element. This can cause an
    // infinite loop as `size` has not yet limited the maxHeight, so the whole
    // page tries to scroll. We must wrap it in rAF.

    // autoUpdate `elementResize` must also be disabled.
    requestAnimationFrame(() => {
      if (activeIndex != null) {
        listRef.current[activeIndex]?.scrollIntoView({ block: "nearest" });
      }
    });
  }, [activeIndex]);

  const { getReferenceProps, getFloatingProps, getItemProps } = useInteractions(
    [
      useRole(context, { role: "listbox" }),
      useDismiss(context),
      useListNavigation(context, {
        listRef,
        activeIndex,
        onNavigate: setActiveIndex,
        virtual: true,
        loop: true
      })
    ]
  );

  function onChange(event: React.ChangeEvent<HTMLInputElement>) {
    const value = event.target.value;
    setInputValue(value);

    if (value) {
      setOpen(true);
      setActiveIndex(0);
    } else {
      setOpen(false);
    }
  }

  const items = data.filter((item) =>
    item.toLowerCase().startsWith(inputValue.toLowerCase())
  );

  return (
    <>
      <input
        {...getReferenceProps({
          ref: reference,
          onChange,
          value: inputValue,
          placeholder: "Enter fruit",
          "aria-autocomplete": "list",
          onKeyDown(event) {
            if (
              event.key === "Enter" &&
              activeIndex != null &&
              items[activeIndex]
            ) {
              setInputValue(items[activeIndex]);
              setActiveIndex(null);
              setOpen(false);
            }

            if (event.key === "Tab") {
              setOpen(false);
            }
          }
        })}
      />
      {open && (
        <div
          {...getFloatingProps({
            ref: floating,
            style: {
              position: strategy,
              left: x ?? "",
              top: y ?? "",
              background: "#eee",
              color: "black",
              overflowY: "auto"
            }
          })}
        >
          <ul>
            {items.map((item, index) => (
              <Item
                {...getItemProps({
                  key: item,
                  ref(node) {
                    listRef.current[index] = node;
                  },
                  onClick() {
                    setInputValue(item);
                    setOpen(false);
                    refs.reference.current?.focus();
                  }
                })}
                active={activeIndex === index}
              >
                {item}
              </Item>
            ))}
          </ul>
        </div>
      )}
    </>
  );
}

export default function App() {
  return (
    <>
      <h1>Floating UI List Autocomplete with Automatic Selection</h1>
      <p>
        <a href="https://www.w3.org/TR/wai-aria-practices-1.1/examples/combobox/aria1.1pattern/listbox-combo.html">
          ARIA 1.1 Combobox with Listbox Popup Examples
        </a>
      </p>
      <AutoComplete />
      <h3>Others</h3>
      <ul>
        <li>
          <a
            href="https://codesandbox.io/s/confident-waterfall-sxcmgs?file=/src/App.tsx"
            target="_blank"
            rel="noreferrer"
          >
            Manual selection
          </a>
        </li>
      </ul>
    </>
  );
}
